/*
* Copyright 2010 Katsunori Koyanagi
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/

package sweezy.anim.easing
{
	import sweezy.anim.core.IEasing;

	public class BezierEasing implements IEasing
	{

		private var _beziers:Array;

		private var _segments:Array;

		public function BezierEasing(points:Array)
		{
			var plen:uint = points.length;
			_beziers = [];
			_segments = [];

			for (var i:int = 0, len:uint = plen - 1; i < len; i++)
			{
				var points0:Array = points[i];
				var points1:Array = points[i + 1];

				_beziers[i] = new BezierSegment(points0.point[0], points0.point[1], points0.post[0], points0.post[1], points1.pre[0], points1.pre[1], points1.point[0], points1.point[1]);
				_segments[i] = points0.point[0];
			}

			_segments[plen - 1] = points[plen - 1].point[0];
		}

		public function ease(fraction:Number):Number
		{
			var bezier:BezierSegment = null;
			var segment:Array = _segments;

			for (var i:int = 0, len:uint = segment.length - 1; i < len; i++)
			{
				if (fraction >= segment[i] && fraction <= segment[i + 1])
				{
					bezier = _beziers[i];
					break;
				}
			}

			return bezier === null ? fraction : bezier.getYForX(fraction);
		}
	}
}

var roots:Array = new Array(4);

class BezierSegment
{

	private var _ax:Number;

	private var _ay:Number;

	private var _bx:Number;

	private var _by:Number;

	private var _cx:Number;

	private var _cy:Number;

	private var _dx:Number;

	private var _dy:Number;

	public function BezierSegment(ax:Number, ay:Number, bx:Number, by:Number, cx:Number, cy:Number, dx:Number, dy:Number)
	{
		_ax = ax;
		_ay = ay;
		_bx = bx;
		_by = by;
		_cx = cx;
		_cy = cy;
		_dx = dx;
		_dy = dy;
	}

	public function getYForX(x:Number):Number
	{
		if (_ax < _dx)
		{
			if (x <= _ax + 0.0000000000000001)
			{
				return _ay;
			}
			if (x >= _dx - 0.0000000000000001)
			{
				return _dy;
			}
		}
		else
		{
			if (x >= _ax + 0.0000000000000001)
			{
				return _ay;
			}
			if (x <= _dx - 0.0000000000000001)
			{
				return _dy;
			}
		}

		var a:Number = -_ax + 3 * _bx - 3 * _cx + _dx;
		var b:Number = 3 * _ax - 6 * _bx + 3 * _cx;
		var c:Number = -3 * _ax + 3 * _bx;
		var d:Number = _ax - x;
		var root:Number;
		var t:Number = NaN;
		getCubicRoots(a, b, c, d);


		var l:uint = roots[0];
		if (l === 0)
		{
			t = 0;
		}
		else if (l === 1)
		{
			t = roots[1];
		}
		else if (l === 2)
		{
			root = roots[1];
			if (0 <= root && root <= 1)
			{
				t = root;
			}
			else
			{
				root = roots[2];
				if (0 <= root && root <= 1)
				{
					t = root;
				}
			}
		}
		else
		{
			root = roots[1];
			if (0 <= root && root <= 1)
			{
				t = root;
			}
			else
			{
				root = roots[2];
				if (0 <= root && root <= 1)
				{
					t = root;
				}
				else
				{
					root = roots[3];
					if (0 <= root && root <= 1)
					{
						t = root;
					}
				}
			}
		}

		return isNaN(t) ? NaN : (t * t * (_dy - _ay) + 3 * (1 - t) * (t * (_cy - _ay) + (1 - t) * (_by - _ay))) * t + _ay;
	}

	private function getCubicRoots(a:Number, b:Number, c:Number, d:Number):void
	{
		var q:Number, tmp:Number, r1:Number, r2:Number, r3:Number;

		if (a === 0)
		{
			if (b === 0)
			{
				if (c === 0)
				{
					roots[0] = 0;
					return;
				}

				roots[0] = 1;
				roots[1] = -d / c;
				return;
			}

			q = c * c - 4 * b * d;
			var signQ:int = (q > 0) ? 1 : q < 0 ? -1 : 0;

			if (signQ < 0)
			{
				roots[0] = 0;
				return;
			}
			else if (signQ === 0)
			{
				roots[0] = 1;
				roots[1] = -c / (2 * b);
				return;
			}

			r1 = r2 = -c / (2 * b);
			tmp = Math.sqrt(q) / (2 * b);
			r1 -= tmp;
			r2 += tmp;

			roots[0] = 2;
			roots[1] = r1;
			roots[2] = r2;
			return;
		}

		if (a !== 1)
		{
			b /= a;
			c /= a;
			d /= a;
		}

		q = (b * b - 3 * c) / 9;
		var qCubed:Number = q * q * q;
		var r:Number = (2 * b * b * b - 9 * b * c + 27 * d) / 54;
		var diff:Number = qCubed - r * r;
		if (diff >= 0)
		{
			if (q === 0)
			{
				roots[0] = 1;
				roots[1] = 0;
				return;
			}

			var theta:Number = Math.acos(r / Math.sqrt(qCubed));
			var qSqrt:Number = Math.sqrt(q);
			r1 = -2 * qSqrt * Math.cos(theta / 3) - b / 3;
			r2 = -2 * qSqrt * Math.cos((theta + 2 * Math.PI) / 3) - b / 3;
			r3 = -2 * qSqrt * Math.cos((theta + 4 * Math.PI) / 3) - b / 3;

			roots[0] = 3;
			roots[1] = r1;
			roots[2] = r2;
			roots[3] = r3;
			return;
		}

		tmp = Math.pow(Math.sqrt(-diff) + (r < 0 ? -r : r), 1 / 3);

		roots[0] = 1;
		roots[1] = ((r > 0) ? -1 : r < 0 ? 1 : 0) * (tmp + q / tmp) - b / 3;
	}
}
